package mymyoux.io.mserveur
{
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.ObjectEncoding;
	import flash.net.Socket;
	import flash.utils.ByteArray;
	
	import mymyoux.core.IObject;
	import mymyoux.events.AMFPHPEvent;
	import mymyoux.events.MEventDispatcher;
	import mymyoux.io.AMFPHP;
	import mymyoux.io.mserveur.events.MBinarySocketEvent;
	import mymyoux.io.mserveur.mapping.PyGram;
	 /**
     * MBinary dispatche l'event connect lorsque le serveur a accepté la connexion après l'identification.
     * @eventType mymyoux.io.mserveur.events.MBinarySocketEvent
     */
    [Event(name="connect", type="mymyoux.io.mserveur.events.MBinarySocketEvent")]
   	 /**
     * MBinary dispatche l'event error lorsqu'une erreur se produit.
     * @eventType mymyoux.io.mserveur.events.MBinarySocketEvent
     */
    [Event(name="error", type="mymyoux.io.mserveur.events.MBinarySocketEvent")]
     /**
     * MBinary dispatche l'event data lors de la réception d'un message.
     * @eventType mymyoux.io.mserveur.events.MBinarySocketEvent
     */
    [Event(name="data", type="mymyoux.io.mserveur.events.MBinarySocketEvent")]
     /**
     * MBinary dispatche l'event disconnect lors de la fermeture du MBinarySocket.
     * @eventType mymyoux.io.mserveur.events.MBinarySocketEvent
     */
    [Event(name="disconnect", type="mymyoux.io.mserveur.events.MBinarySocketEvent")]
	 /**
     * MBinary dispatche l'event bad_server lorsque le serveur n'est plus référencé dans la BDD.
     * @eventType mymyoux.io.mserveur.events.MBinarySocketEvent
     */
    [Event(name="bad_server", type="mymyoux.io.mserveur.events.MBinarySocketEvent")]	
	/**
	 * Socket binaire utilisé par MAdvBinSocket pour se connecter au serveur Python (binaire et AMF)
	 * <p><b>N'est normalement pas utilisé directement.</b></p>
	 */
	public class MBinarySocket extends MEventDispatcher implements IObject
	{
		/**
		 * Socket utilisé pour se connecter au serveur binaire.
		 */
		protected var socket:Socket;
		/**
		 * @private
		 */
		protected var _connected:Boolean;
		/**
		 * Idx du client.
		 */
		protected var idx:uint;
		/**
		 * Préfixe utilisé sur le serveur pour différencier les applications.
		 */
		protected var pre_idx:String;
		/**
		 * AMFPHP permettant la liaison avec le serveur PHP.
		 */
		protected var amfphp:AMFPHP;
		/**
		 * @private
		 */
		protected var _ip:String;
		/**
		 * @private
		 */
		protected var _port:uint;
		/**
		 * Mot de passe demandé par le serveur (socket) pour valider la connexion.
		 * Il est récupéré par le serveur AMFPHP.
		 */
		protected var password:String;
		/**
		 * Compteur du nombre d'erreur, permet de modifier le comportement du MBinarySocket
		 * Au bout d'un trop grand nombre d'erreur, notamment en allant vérifier le mot de passe auprès du serveur AMFPHP.
		 */
		protected var numError:uint;
		/**
		 * Créer un objet de type MBinarySocket qui va se connecter à un serveur (socket) binaire
		 * utilisant le protocole AMF.
		 * @param ip IP du serveur.
		 * @param port Port du serveur.
		 * @param idx Idx du client.
		 * @param pre_idx Préfixe utilisé sur le serveur pour différencier les applications.
		 * @param amfphp AMFPHP permettant la liaison avec le serveur PHP.
		 */
		public function MBinarySocket(ip:String,port:uint,idx:uint,pre_idx:String,amfphp:AMFPHP)
		{
			if(ip.length==0 || port<=0 || port>65535 || amfphp==null)
			{
				throw(new Error("ip, port ou AMFPHP incorrects"));
			}
			_connected=false;
			this.amfphp=amfphp;
			this.idx=idx;
			this.pre_idx=pre_idx;
			_port=port;
			_ip=ip;
			numError=0;
			getPassword();
		}
		/**
		 * IP du serveur.
		 */
		public function get ip():String
		{
			return _ip;
		}
		/**
		 * Port du serveur.
		 */
		public function get port():uint
		{
			return _port;
		}
		/**
		 * Appelée pour récupérer le password de connexion au serveur (socket).
		 */
		protected function getPassword():void
		{
			amfphp.call("getPassword",onGetPassword,idx,ip,port);
		}
		/**
		 * Appelée par amfphp lors de la réception du password de connexion.
		 * Si le résultat est un échec, <code><b>getPassword()</b></code> est rappelée au bout de 5 secondes,
		 * sinon le tampon est vidé.
		 * @param event AMFPHPEvent contenant le password si la transaction s'est bien passée.
		 */
		 protected function onGetPassword(event:AMFPHPEvent):void
		{

		
			if(event.success)
			{
				password=event.value;/*
				while(tampon.hasNext())
				{
					var func:Fonction=tampon.next() as Fonction;
					func.func.apply(this,func.params);
				}*/
				connect();
			}
			else
			{
				var message:PyGram=new PyGram();
				message.action=MBinarySocketEvent.BAD_SERVER;
				message.parameters.push(ip);
				message.parameters.push(port);
				dispatchEvent(new MBinarySocketEvent(MBinarySocketEvent.BAD_SERVER,message));
			}
		}
		/**
		 * Active la connexion avec le serveur (socket).
		 * Appelée lors de la récupération du mot de passe pour le serveur.
		 * @see #onGetPassword()
		 */
		protected function connect():void
		{
			socket=new Socket(ip,port);
			socket.objectEncoding=ObjectEncoding.AMF3;
			loadListeners();
		}
		/**
		 * Permet d'écouter tous les évènements liés au Socket.
		 */
		protected function loadListeners():void
		{
			socket.addEventListener(Event.CONNECT,onConnect);
			socket.addEventListener(IOErrorEvent.IO_ERROR,onIOError);
			socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR,onSecurityError);
			socket.addEventListener(ProgressEvent.SOCKET_DATA,onData);
			socket.addEventListener(Event.CLOSE,onClose);
		}
		/**
		 * Permet de retirer de l'écoute tous les évènements liés au Socket.
		 */
		protected function unloadListeners():void
		{
			_connected=false;
			socket.removeEventListener(Event.CONNECT,onConnect);
			socket.removeEventListener(IOErrorEvent.IO_ERROR,onIOError);
			socket.removeEventListener(SecurityErrorEvent.SECURITY_ERROR,onSecurityError);
			socket.removeEventListener(ProgressEvent.SOCKET_DATA,onData);
			socket.removeEventListener(Event.CLOSE,onClose);
		}
		/**
		 * Envoie un PyGram au serveur.
		 * C'est un message formatté compris par celui.
		 * Il est autosuffisant : Il comprend le type, le message et le ou les destinataires.
		 * @param message Le Message.
		 * @see mymyoux.io.mserveur.mapping.PyGram
		 */
		public function write(message:PyGram):void
		{
			try{
				socket.writeObject(message);
				socket.flush();
			}catch(error:Error)
			{
				trace("message non envoyé : "+message+" due à : "+error);
			}
			
			
		}
		/**
		 * Indique si le socket est connecté ou non.
		 */
		public function get connected():Boolean
		{
			return socket!=null && socket.connected && _connected && password!=null && password.length>0;
		}
		/**
		 * Appelée par le Socket lorsque la connexion au serveur réussie.
		 * @param event Evenement dispatché par le socket.
		 */
		protected function onConnect(event:Event):void
		{
			
			var message:PyGram=new PyGram();
			message.action=PyGram.CALL_IDENTIFICATION;
			message.parameters.push(pre_idx+idx,password);
			write(message);
		}
		 /**
		 * Appelée par le Socket lorsque la connexion rencontre des erreurs.
		 * @param event Evenement dispatché par le socket.
		 * @see #error()
		 */
		protected function onIOError(event:IOErrorEvent):void{
			var p:PyGram=new PyGram();
			p.action=PyGram.IO_ERROR;
			error(p);
		}
		 /**
		 * Appelée en interne par MBinarySocket lorsqu'une erreur avec le socket est rencontrée.
		 * @param message Message indiquant l'erreur.
		 * @see #onIOError()
		 * @see #onSecurityError()
		 * @see mymyoux.io.mserveur.events.MBinarySocketEvent#error
		 */
		protected function error(message:PyGram):void
		{
			password=null;
			if(socket!=null)
			{
				unloadListeners();
				socket.close();
			}
			
			if(numError++<500)
			{
				getPassword();
				return;
			}else
			{
				dispatchEvent(new MBinarySocketEvent(MBinarySocketEvent.ERROR,message));
				numError=0;
			}
		}
		 /**
		 * Appelée par le Socket lorsque la connexion rencontre des erreurs liées à la sécurité.
		 * @param event Evenement dispatché par le socket.
		 * @see #error()
		 */
		protected function onSecurityError(event:SecurityErrorEvent):void{
			var p:PyGram=new PyGram();
			p.action=PyGram.SECURITY_ERROR;
			error(p);
		}
		 /**
		 * Appelée par le Socket lorsque de la réception de données.
		 * <p>Le message se retrouve sous forme de PyGram</p>
		 * @param event Evenement dispatché par le socket.
		 * @see #error()
		 * @see mymyoux.io.mserveur.mapping.PyGram
		 */
		protected function onData(event:ProgressEvent):void{
			while(socket.bytesAvailable>0){
				try
				{
					var message:PyGram=socket.readObject() as PyGram;
					if(message.isError())
					{
						//dispatchError(message);
						if(message.action==PyGram.BAD_IDENTIFICATION || message.action==PyGram.TIMEOUT)
						{
							error(message);
							return;							
						}
						dispatchEvent(new MBinarySocketEvent(MBinarySocketEvent.ERROR,message));
						//interdit
						//return;
					}
					else
					{
						if(message.action==PyGram.GOOD_IDENTIFICATION)
						{
							_connected=true;
							numError=0;
							this.dispatchEvent(new MBinarySocketEvent(MBinarySocketEvent.CONNECT,message));
							
						}else
						{
							
							if(message.action=="send")
							{
								//essai de décompression du bytearray
								var b:ByteArray=message.parameters[2];
								var value:*;
								try
								{
									b.uncompress();
								}catch(error:Error)
								{
									
								}
								value=b.readObject();
								message.parameters[2]=value;
								this.dispatchEvent(new MBinarySocketEvent(MBinarySocketEvent.DATA,message));

							}else
							{
								this.dispatchEvent(new MBinarySocketEvent(message.action,message));
							}
						}
					}
				}catch(error:Error)
				{
					trace("ErrorMsg [MBinarySocket]:"+error);
				}
			}
		}
		 /**
		 * Appelée par le Socket lorsque la connexion est fermée de manière innatendue.
		 * @param event Evenement dispatché par le socket.
		 * @see #error()
		 */
		protected function onClose(event:Event):void{
			var p:PyGram=new PyGram();
			p.action=PyGram.SECURITY_ERROR;
			error(p);
		}
		/**
		 * @inheritDoc
		 */
		public override function equals(objet:IObject):Boolean
		{
			try
			{
				var s:MBinarySocket=objet as MBinarySocket;
				if(s.ip==ip && s.port==port)
				{
					return true;
				}
			}catch(error:Error)
			{
				
			}
			return false;
		}
		/**
		 * @inheritDoc
		 */
		public override function destroy():void
		{
			_connected=false;
			var message:PyGram=new PyGram();
			message.action=MBinarySocketEvent.DISCONNECT;
			message.parameters=new Array("destroy");
			this.dispatchEvent(new MBinarySocketEvent(MBinarySocketEvent.DISCONNECT,message));
			super.destroy();
		}
		/**
		 * @inheritDoc
		 */
		public override function toString():String
		{
			return "[MBinarySocket ip=\""+ip+"\" port=\""+port+"\"]";
		}
	}
}